/* Copyright (c) 2011 Stephan Zehrer
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "XMLDocumentStore.h"

#import "NSXMLElement+XMLTools.h"


@implementation XMLDocumentStore

NSString *KSNextID = @"nextID";
NSString *KSAttributeID = @"id";
NSString *KSFormatID = @"i%ld";



- (NSXMLElement *) parentForObject:(NSManagedObject *)managedObject {
	
	//NSEntityDescription *entity = [managedObject entity];
	
	// TODO 
	return nil;
}


#pragma mark New Reference Objects

- (NSXMLElement *) newElementForEntity:(NSEntityDescription *)entity {
	
	// create XML element
	NSXMLElement *objElement = [[NSXMLElement alloc] initWithName:[entity name]];	
	
	// create ID string and increase nextID
	NSString *nextIDValue = [NSString stringWithFormat:KSFormatID, nextID];
	nextID++;
	
	// create ID attribute node
	NSXMLNode *idAttribute = [NSXMLNode attributeWithName:KSAttributeID stringValue:nextIDValue];
	[objElement addAttribute:idAttribute];
	
	return objElement;
}

#pragma mark load & save

- (BOOL)load:(NSError **)error {
	
    if (self.document != nil) {
		
		NSMutableSet *cacheNodes = [NSMutableSet set];
		
		NSXMLNode *aNode = [self.document rootElement];
		id refID = nil; 
		
		do {
			if ([aNode kind] == NSXMLElementKind ) {
				
				refID = [(NSXMLElement*)aNode attributeForName:KSAttributeID];
				
				if ( refID != nil) {
					// load the data & create the cachNode 
					[cacheNodes addObject:[self loadEntity:(NSXMLElement*)aNode withID:refID]];
				}
			}
		} while (aNode = [aNode nextNode]);
		
		[self addCacheNodes:cacheNodes];
	}
    
    return (self.document != nil);
}


#pragma mark updateCachNodes

- (void)updateAttributesInCacheNode:(NSAtomicStoreCacheNode *)node andElement:(NSXMLElement *)element fromManagedObject:(NSManagedObject *)managedObject {
	
	NSDictionary *attributesByName = [[managedObject entity] attributesByName];
	NSArray *attributeKeys = [attributesByName allKeys];

	// create or update the attribtues in the document
	for (NSString *name in attributeKeys) {
		
		NSAttributeDescription *attributeDescription =  [attributesByName objectForKey:name];
		
		if ([attributeDescription isTransient]) continue;
		
		NSAttributeType attributeType = [ attributeDescription attributeType];
		 
		id valueToStore = [managedObject valueForKey:name];
		
		NSXMLElement *attributeElement = [element findOrAddElementForName:name];

		switch (attributeType) {
			case NSFloatAttributeType:
				[attributeElement setObjectValue:[valueToStore stringValue]];
				break;
//			case NSDateAttributeType:
				// TODO
//				break;
//			case NSBinaryDataAttributeType:
				//TODO
//				break;
			default:
				[attributeElement setObjectValue:valueToStore];
				break;
		}
	}
	
	// update the cacheNode
	[node setValuesForKeysWithDictionary:[managedObject dictionaryWithValuesForKeys:attributeKeys]];
	
	/*
	 
	   if (attributeType == NSBinaryDataAttributeType) {
			BOOL writeBytes = YES;
			if ([columnData childCount] == 1) {
				content = (NSXMLElement *)[columnData childAtIndex:0];
			}
			
			// if the original content was a link, don't just overwrite it. Try to write data back to the url first.
			if ((content != nil) && ([[content name] isEqualToString:@"a"])) {
				NSString *hrefValue = [[content attributeForName:@"href"] objectValue];
				if (hrefValue != nil) {
					NSURL *url = [NSURL URLWithString:hrefValue];
					if ([valueToStore writeToURL:url atomically:YES] == YES) {
						writeBytes = NO;
					}
				}
			}
			if (writeBytes == YES) {
				// setting up the node this way encodes the NSData as bsae64 in data attribute
				content = [NSXMLElement elementWithName:@"object"];
				NSXMLNode *dataAttribute = [NSXMLNode attributeWithName:@"data" stringValue:nil];
				[dataAttribute setObjectValue:valueToStore];
				[content addAttribute:dataAttribute];
				[content setStringValue:@"binary object value"];
			}
		}     
    
	*/
	
}


- (void)updateRelationshipsInCacheNode:(NSAtomicStoreCacheNode *)node andElement:(NSXMLElement *)element fromManagedObject:(NSManagedObject *)managedObject {
	
	
	NSDictionary *relationshipsByName = [[managedObject entity] relationshipsByName];
	NSArray *relationshipKeys = [relationshipsByName allKeys];
	
	// create or update the attribtues in the document
	for (NSString *name in relationshipKeys) {
		
		NSMutableSet *referenceCacheNodes = [[NSMutableSet alloc] init];
		
		NSXMLElement *relationshipElement = [element elementForName:name];
		
		id referencedObjects = [managedObject valueForKey:name];
		
 		if (referencedObjects == nil) {

			if (relationshipElement == nil) {
				[element addChildElementWithName:name];
			}
			
			NSXMLNode *refNode = nil;
			
			NSRelationshipDescription *relationshipDescription = [relationshipsByName objectForKey:name];
			if (([relationshipDescription isToMany] == NO) ) {
				// this is to-One relationship (in this case referencedObjects point to the object)
				referencedObjects = [NSSet setWithObject:referencedObjects];
				
				refNode = [relationshipElement findOrAddAttributeForName:@"ref"];
			} else {
				refNode = [relationshipElement findOrAddAttributeForName:@"refs"];
			}

			NSMutableArray *refData = [[NSMutableArray alloc] init];
			
			for (NSManagedObject *destinationObject in referencedObjects ) {
				id refID = [self referenceObjectForObjectID:[destinationObject objectID]];
				
				[refData addObject:refID];
				
				id destinationNode = [self cacheNodeForEntity:[destinationObject entity] withReferenceData:refID];
				[referenceCacheNodes addObject:destinationNode];
			}
			
			[refNode setObjectValue:refData];
			[refData release];
		
		} else {
			
			// remove relationship element form object element
			[relationshipElement detach];
		}
		
		// 	update cache node
		[node setValue:referenceCacheNodes forKey:name];
		[referenceCacheNodes release];
	}
}

 
#pragma mark Properties

- (NSString *)type {
    return @"XMLDocumentStore";
}

#pragma mark Metadata 

- (void)readMetadata {
	
    NSMutableDictionary *metadata = [NSMutableDictionary dictionary];
    
    for (NSXMLElement *node in [[self document] children]) {
		if ([node kind] == NSXMLProcessingInstructionKind) {
			
			NSString *metaName = [node name];

			if (metaName != nil) {
				id metaContent = [node value];

				// test if the meta information include a propertyList
				@try {
					metaContent = [metaContent propertyList];
				} @catch (NSException *parseException) {}
				
				if ([metaName isEqualToString:KSNextID]) {
					nextID = [metaContent integerValue];
				} else {
					[metadata setObject:metaContent forKey:metaName];
				}
    
			}		
		}
    }
    [self setMetadata:metadata];
	
} 

- (void)writeMetadata {
	
	// remove all old meta entries
    for (NSXMLElement *node in [[self document] children]) {
		if ([node kind] == NSXMLProcessingInstructionKind) {
			[node detach];
		}
	}
	
    // add meta data entries 
    NSDictionary *metadata = [self metadata];    
    for (NSString *name in [metadata allKeys]) {
		
		NSString *value = [[metadata objectForKey:name] description];
		
		NSXMLNode *metaNode =[NSXMLNode processingInstructionWithName:name stringValue:value];
	    [[self document] insertChild:metaNode atIndex:0];
    }
	
	// write nextID value
	NSXMLNode *metaNode =[NSXMLNode processingInstructionWithName:KSNextID stringValue:[NSString stringWithFormat:KSFormatID, nextID]];
	[[self document] insertChild:metaNode atIndex:0];
	
}

@end
